import fnmatch
import os
import sys
import zipfile
import argparse
from datetime import datetime
from pathlib import Path
import re

from logger import Logger
from util_modified_folder import find_recently_modified_folders
from util_path import remove_first_part_pathlib_parts


class GitIgnorePackager:
    def __init__(self, source_dir, output_zip):
        self.source_dir = Path(source_dir).resolve()
        self.output_zip = Path(output_zip)
        self.ignore_patterns = []
        self.gitignore_path = self.source_dir / '.gitignore'

    def parse_gitignore(self):
        """解析.gitignore文件，提取忽略模式[3,5](@ref)"""
        if not self.gitignore_path.exists():
            print("未找到.gitignore文件，将打包所有文件")
            return

        with open(self.gitignore_path, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                # 跳过空行和注释[5](@ref)
                if not line or line.startswith('#'):
                    continue
                # 处理转义字符[5](@ref)
                if line.startswith('\\'):
                    line = line[1:]
                self.ignore_patterns.append(line)

        print(f"已加载 {len(self.ignore_patterns)} 个忽略规则")

    def should_ignore_advanced(self, file_path):
        """更完整的.gitignore规则实现"""
        relative_path = file_path.relative_to(self.source_dir)
        relative_posix = relative_path.as_posix()

        for pattern in self.ignore_patterns:
            # 处理否定规则（以!开头）[5](@ref)
            if pattern.startswith('!'):
                negated_pattern = pattern[1:]
                # 实现否定逻辑
                continue

            # 处理目录匹配[5](@ref)
            if pattern.endswith('/'):
                dir_pattern = pattern.rstrip('/')
                if relative_path.is_dir() and str(relative_path) == dir_pattern:
                    return True
                if relative_posix.startswith(dir_pattern + '/'):
                    return True

            # 实现完整的glob模式匹配
            # ... 更复杂的模式匹配逻辑

    def should_ignore(self, file_path):
        """检查文件是否应该被忽略[3,5](@ref)"""
        # 获取相对于源目录的路径
        relative_path = file_path.relative_to(self.source_dir)
        relative_posix = relative_path.as_posix()

        for pattern in self.ignore_patterns:
            # 处理目录模式（以/结尾）
            if pattern.endswith('/'):
                dir_pattern = pattern.rstrip('/')
                if relative_posix.startswith(dir_pattern + '/') or str(relative_path) == dir_pattern:
                    return True

            # 处理通配符匹配[5](@ref)
            pattern_re = re.escape(pattern)
            pattern_re = pattern_re.replace('\\*', '.*').replace('\\?', '.')
            pattern_re = pattern_re.replace('\\[', '[').replace('\\]', ']')

            # 如果模式以/开头，只匹配根目录[5](@ref)
            if pattern.startswith('/'):
                if re.fullmatch(pattern_re[1:], relative_posix):
                    return True
            else:
                # 匹配任意位置的模式[5](@ref)
                if re.search(pattern_re, relative_posix):
                    return True

        return False

    def collect_files(self):
        """收集需要打包的文件[7](@ref)"""
        print(f"collecting files from {self.source_dir}")
        valid_files = []

        for root, dirs, files in os.walk(self.source_dir):
            root_path = Path(root)

            # 跳过.git目录[4,8](@ref)
            if '.git' in dirs:
                dirs.remove('.git')

            for file in files:
                file_path = root_path / file

                # 跳过.gitignore文件本身（可选）
                if file == '.gitignore':
                    continue

                # 检查是否应该忽略
                if not self.should_ignore(file_path):
                    valid_files.append(file_path)
                    # print(f"包含: {file_path.relative_to(self.source_dir)}")
                else:
                    pass
                    # print(f"忽略: {file_path.relative_to(self.source_dir)}")

        return valid_files

    @staticmethod
    def zip_folder(folder_path, output_path):
        """
        将指定文件夹打包为ZIP文件

        Parameters:
            folder_path (str): 要打包的文件夹路径
            output_path (str): 生成的ZIP文件路径
        """
        pass
        with open(output_path, 'w'):
            pass
        with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
            for root, dirs, files in os.walk(folder_path):
                for file in files:
                    # 获取文件的完整路径
                    file_path = os.path.join(root, file)
                    # 计算文件在ZIP包中的相对路径，避免保留完整的本地目录结构
                    arcname = os.path.relpath(file_path, folder_path)
                    # 将文件写入ZIP
                    zipf.write(file_path, arcname)

    def create_zip(self, files):
        """创建ZIP文件[7](@ref)"""
        print(f"creating zip file {self.output_zip}")
        print(f"创建ZIP文件 {self.output_zip}")
        with open(self.output_zip, 'w'):
            pass
        with zipfile.ZipFile(self.output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
            for file_path in files:
                # 在ZIP中使用相对路径
                arcname = file_path.relative_to(self.source_dir)
                zipf.write(file_path, arcname)

        print(f"\n已创建ZIP文件: {self.output_zip}")
        print(f"包含 {len(files)} 个文件")
        # if not os.path.exists(self.output_zip):
        #     print(f"创建ZIP文件 {self.output_zip}")
        #     with open(self.output_zip, 'w'):
        #         pass
        #     with zipfile.ZipFile(self.output_zip, 'w', zipfile.ZIP_STORED) as zipf:
        #         for file_path in files:
        #             # 在ZIP中使用相对路径
        #             arcname = file_path.relative_to(self.source_dir)
        #             zipf.write(file_path, arcname)
        #
        #     print(f"\n已创建ZIP文件: {self.output_zip}")
        #     print(f"包含 {len(files)} 个文件")
        # else:
        #     print(f"skipped111, project zip {self.output_zip} already exists.")

    def package(self):
        """执行打包流程"""
        print(f"源目录: {self.source_dir}")
        print(f"输出文件: {self.output_zip}")
        print("=" * 50)

        self.parse_gitignore()
        files = self.collect_files()
        self.create_zip(files)


if __name__ == "__main__":
    overwrite = True
    overwrite_package = True
    enable_modified_threshold = True
    modified_threshold = 10 # 3天


    if not os.path.isdir(f"assets"):
        os.mkdir(f"assets")
    if not os.path.isdir(f"assets/log"):
        os.mkdir(f"assets/log")
    sys.stdout = Logger(datetime.now().strftime(f"assets/log/%Y%m%d_%H%M%S") + ".txt")

    ignore_folder_list = [".*", "__pycache__", "*.tmp", "node_modules"]
    # source_dir = r"D:\projects\kp-playnite"

    # scan_base_path = r"D:\projects"
    output_base_path = r"E:\projects_collector"
    # 检查源目录是否存在

    project_list = [
        r"D:\projects\kp-webarchives",
        r"D:\projects\kp-slash",
        r"D:\projects\kp-playnite",
        r"D:\projects\kp-backup",
        r"D:\projects\kp-fitgirl",
        r"D:\projects\kp-torrents",
        r"D:\projects\obsidian_plugins",
        r"D:\projects\chrome_plugins",
        r"D:\projects\kp-btbbt",
        r"D:\projects\kp-aws",
        # r"D:\projects\obsidian_plugins\obsidian-cubox-1.0.3",
    ]
    include_project_folder_list = []
    for project in project_list:
        source_dir = project
        # 使用os.walk递归遍历源目录及其所有子目录
        for root, dirs, files in os.walk(source_dir):
            # 递归子目录
            dirs[:] = [d for d in dirs if not any(fnmatch.fnmatch(d, pattern) for pattern in ignore_folder_list)]
            # 仅处理.git仓库的文件夹
            if os.path.exists(os.path.join(root, ".git")):
                if enable_modified_threshold:
                    if len(find_recently_modified_folders(root, days=modified_threshold ,include_subfolders=False)) > 0:
                        include_project_folder_list.append(root)
                else:
                    include_project_folder_list.append(root)

    print(f"需要处理 {len(include_project_folder_list)} 个项目...")
    for project_folder in include_project_folder_list:
        for root, dirs, files in os.walk(project_folder):
            dirs[:] = [d for d in dirs if not any(fnmatch.fnmatch(d, pattern) for pattern in ignore_folder_list)]
            source_dir = root
            output_dir_name = str(remove_first_part_pathlib_parts(source_dir))
            output_zip_name = os.path.basename(source_dir)

            ### 打包项目主体源码
            if os.path.exists(os.path.join(root, ".git")):
                Path(os.path.join(output_base_path, output_dir_name)).mkdir(parents=True, exist_ok=True)
                project_zip_path = os.path.join(output_base_path, output_dir_name, output_zip_name + ".zip")
                if overwrite or not os.path.exists(project_zip_path):
                    packager = GitIgnorePackager(source_dir, project_zip_path)
                    packager.package()
                else:
                    print(f"skipped, project zip {project_zip_path} already exists.")

                ### 打包.git文件夹
                git_repo_zip_path = os.path.join(output_base_path, output_dir_name, output_zip_name + ".git" + ".zip")
                if overwrite or not os.path.exists(git_repo_zip_path):
                    print(f"packaging {git_repo_zip_path}")
                    GitIgnorePackager.zip_folder(os.path.join(source_dir, ".git"), git_repo_zip_path)
                else:
                    print(f"skipped, .git {git_repo_zip_path} already exists.")

            ### 打包node_modules文件夹，如果这个文件夹存在
            if os.path.exists(os.path.join(source_dir, "node_modules")):
                need_package_node_modules = False
                if enable_modified_threshold:
                    if len(find_recently_modified_folders(os.path.join(source_dir, "node_modules"), days=modified_threshold ,include_subfolders=False)) > 0:
                        need_package_node_modules = True
                else:
                    need_package_node_modules = True

                if need_package_node_modules:
                    Path(os.path.join(output_base_path, output_dir_name)).mkdir(parents=True, exist_ok=True)
                    # if not os.path.isdir(os.path.join(output_base_path, output_dir_name)):
                    #     os.mkdir(os.path.join(output_base_path, output_dir_name))
                    note_modules_path = os.path.join(output_base_path, output_dir_name, output_zip_name + ".node_modules" + ".zip")
                    if overwrite_package or not os.path.exists(note_modules_path):
                        print(f"packaging {note_modules_path}")
                        GitIgnorePackager.zip_folder(os.path.join(source_dir, "node_modules"), note_modules_path)
                    else:
                        print(f"skipped, node_modules {note_modules_path} already exists.")
                    pass

            ### 打包venv文件夹，如果这个文件夹存在
            if os.path.exists(os.path.join(source_dir, "venv")):
                need_package_venv = False
                if enable_modified_threshold:
                    if len(find_recently_modified_folders(os.path.join(source_dir, "node_modules"), days=modified_threshold, include_subfolders=False)) > 0:
                        need_package_venv = True
                else:
                    need_package_venv = True

                if need_package_venv:
                    Path(os.path.join(output_base_path, output_dir_name)).mkdir(parents=True, exist_ok=True)
                    venv_path = os.path.join(output_base_path, output_dir_name, output_zip_name + ".venv" + ".zip")
                    if overwrite_package or not os.path.exists(venv_path):
                        print(f"packaging {venv_path}")
                        GitIgnorePackager.zip_folder(os.path.join(source_dir, "venv"), venv_path)
                    else:
                        print(f"skipped, venv {venv_path} already exists.")
                    pass